package com.ldf.callback;

import com.aizuda.zlm4j.structure.MK_MEDIA;
import com.ldf.core.HCNetSDK;
import com.sun.jna.CallbackThreadInitializer;
import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.Pointer;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;

import static com.ldf.RecordPlayDemo.ZLM_API;

/**
 * 回放流回调
 */
public class FPlayDataCallback implements HCNetSDK.FPlayDataCallBack {
    private final MK_MEDIA mkMedia;
    private final Memory buffer = new Memory(1024 * 1024);
    //private final Memory audioBuffer = new Memory(1024 * 16);
    private int bufferSize = 0;
    private int audioBufferSize = 0;
    private int audioDataSize = 0;
    private long pts;
    private double fps;
    private double multiplier;
    private long time_base;
    private long last_send_time = 0;
    private int videoType = 0;
    private int audioType = 0;
    private PipedInputStream pipedInputStream;
    private PipedOutputStream pipedOutputStream;
    private BufferedInputStream inputStream;
    private boolean isStart = false;

    public FPlayDataCallback(MK_MEDIA mkMedia, double fps, double multiplier) {
        this.mkMedia = mkMedia;
        this.fps = fps;
        this.multiplier = multiplier;
        //ZLM以1000为时间基准
        time_base = (long) (1000 / (fps * multiplier));
        this.pipedOutputStream = new PipedOutputStream();
        this.pipedInputStream = new PipedInputStream(1024 * 1024 * 5);
        try {
            pipedInputStream.connect(pipedOutputStream);
        } catch (IOException e) {
            throw new RuntimeException("创建输入管道失败");
        }
        //回调使用同一个线程
        Native.setCallbackThreadInitializer(this, new CallbackThreadInitializer(true, false, "HikPlayBackStream"));
    }

    @Override
    public void invoke(long lPlayHandle, int dwDataType, Pointer pointer, int dwBufSize, int dwUser) {
        if (dwDataType == HCNetSDK.NET_DVR_STREAMDATA) {
            try {
                pipedOutputStream.write(pointer.getByteArray(0, dwBufSize));
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (!isStart) {
                new Thread(() -> {
                    try {
                        Thread.sleep(200);
                        inputStream = new BufferedInputStream(pipedInputStream, 1024 * 1024 * 5);
                        while (true) {
                            readData();
                        }
                    } catch (IOException | InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }).start();
                isStart = true;
            }
        }
    }

    /**
     * 读取数据
     */
    private void readData() throws IOException, InterruptedException {
        readPSHAndPSMAndPSMT();
        readPES();
        readHkPrivateData();
    }

    /**
     * 读取pes及数据
     */
    private void readPES() throws IOException, InterruptedException {
        //pes header
        byte[] pesHeaderStartCode = new byte[4];
        inputStream.mark(4);
        inputStream.read(pesHeaderStartCode);
        if ((pesHeaderStartCode[0] & 0xFF) == 0x00 && (pesHeaderStartCode[1] & 0xFF) == 0x00 && (pesHeaderStartCode[2] & 0xFF) == 0x01) {
            int streamType = pesHeaderStartCode[3] & 0xFF;
            //视频流
            if (streamType >= 0xE0 && streamType <= 0xEF) {
                //视频数据
                readVideoES();
            } else if (streamType >= 0xC0 & streamType <= 0xDF) {
                //音频数据
                readAudioES();
            } else {
                byte[] lengthBytes = new byte[2];
                inputStream.read(lengthBytes);
                int length = ((lengthBytes[0] & 0xFF) << 8) | (lengthBytes[1] & 0xFF);
                inputStream.skip(length);
            }
        } else {
            inputStream.reset();
        }
    }

    /**
     * 读取h264视频数据
     */
    private void readVideoES() throws IOException, InterruptedException {
        byte[] pesLengthByte = new byte[2];
        inputStream.read(pesLengthByte);
        int pesLength = (pesLengthByte[0] & 0xFF) << 8 | (pesLengthByte[1] & 0xFF);
        //pes数据
        if (pesLength > 0) {
            byte[] pts_dts_length_info = new byte[3];
            inputStream.read(pts_dts_length_info);
            int pesHeaderLength = (pts_dts_length_info[2] & 0xFF);
            //判断是否是有pts 忽略dts
            int i = (pts_dts_length_info[1] & 0xFF) >> 6;
            if (i == 0x02 || i == 0x03) {
                //byte[] pts_dts = new byte[5];
                //pointer.read(offset, pts_dts, 0, pts_dts.length);
                //这里获取的是以90000为时间基的 需要转为 1/1000为基准的 但是pts还是不够平滑导致画面卡顿 所以不采用读取的pts
                //long pts_90000 = ((pts_dts[0] & 0x0e) << 29) | (((pts_dts[1] << 8 | pts_dts[2]) & 0xfffe) << 14) | (((pts_dts[3] << 8 | pts_dts[4]) & 0xfffe) >> 1);
                pts = time_base + pts;
            }
            inputStream.skip(pesHeaderLength);
            byte[] naluStart = new byte[5];
            inputStream.mark(naluStart.length);
            inputStream.read(naluStart);
            if ((naluStart[0] & 0xFF) == 0x00 && (naluStart[1] & 0xFF) == 0x00 && (naluStart[2] & 0xFF) == 0x00 && (naluStart[3] & 0xFF) == 0x01) {
                if (bufferSize > 0) {
                    int naluType = buffer.getByte(4) & 0x1F;
                    if (naluType != 7 && naluType != 8) {
                        long now = System.currentTimeMillis();
                        if (last_send_time != 0) {
                            long time = now - last_send_time;
                            if (time_base > time && time >= 0) {
                                long waitTime = time_base - time;
                                Thread.sleep(waitTime);
                            }
                        }
                        last_send_time = now;
                    }
                    if (videoType == 0x1B) {
                        ZLM_API.mk_media_input_h264(mkMedia, buffer.share(0), bufferSize, pts, pts);
                    } else if (videoType == 0x24) {
                        ZLM_API.mk_media_input_h265(mkMedia, buffer.share(0), bufferSize, pts, pts);
                    }
                    bufferSize = 0;
                }
            }
            inputStream.reset();
            int videoLength = pesLength - pts_dts_length_info.length - pesHeaderLength;
           /* while (inputStream.available() < videoLength) {
                System.out.println("视频还是需要等待");
                Thread.sleep(time_base);
            }*/
            byte[] temp = new byte[videoLength];
            inputStream.read(temp);
            buffer.write(bufferSize, temp, 0, videoLength);
            bufferSize += videoLength;
        }
    }

    /**
     * 读取音频数据
     */
    private void readAudioES() throws IOException, InterruptedException {
        byte[] pesLengthByte = new byte[2];
        inputStream.read(pesLengthByte);
        int pesLength = (pesLengthByte[0] & 0xFF) << 8 | (pesLengthByte[1] & 0xFF);
        //pes数据
        if (pesLength > 0) {
            byte[] pts_dts_length_info = new byte[3];
            inputStream.read(pts_dts_length_info);
            int pesHeaderLength = (pts_dts_length_info[2] & 0xFF);
            //判断是否是有pts 忽略dts
            int i = (pts_dts_length_info[1] & 0xFF) >> 6;
            long pts_90000 = 0;
            if (i == 0x02 || i == 0x03) {
                byte[] pts_dts = new byte[5];
                inputStream.mark(pts_dts.length);
                inputStream.read(pts_dts);
                inputStream.reset();
                //这里获取的是以90000为时间基的 需要转为 1/1000为基准的 但是pts还是不够平滑导致画面卡顿 所以不采用读取的pts
                pts_90000 = ((pts_dts[0] & 0x0e) << 29) | (((pts_dts[1] << 8 | pts_dts[2]) & 0xfffe) << 14) | (((pts_dts[3] << 8 | pts_dts[4]) & 0xfffe) >> 1);
                //pts = time_base + pts;
            }
            inputStream.skip(pesHeaderLength);
            int audioLength = pesLength - pts_dts_length_info.length - pesHeaderLength;
   /*         while (inputStream.available() < audioLength) {
                Thread.sleep(time_base);
            }*/
            inputStream.skip(audioLength);
            /*byte[] temp = new byte[audioLength];
            inputStream.read(temp);
            byte[] bytes = G711ACodec._toPCM(temp);
            audioBuffer.write(0, bytes, 0, bytes.length);
            ZLM_API.mk_media_input_pcm(mkMedia, audioBuffer.share(0), bytes.length, pts);*/
        }
    }

    private void readHkPrivateData() throws IOException {
        byte[] psHkPrivateHeader = new byte[4];
        inputStream.mark(psHkPrivateHeader.length);
        inputStream.read(psHkPrivateHeader);
        if ((psHkPrivateHeader[0] & 0xFF) == 0x00 && (psHkPrivateHeader[1] & 0xFF) == 0x00 && (psHkPrivateHeader[2] & 0xFF) == 0x01 && (psHkPrivateHeader[3] & 0xFF) == 0xBD) {
            byte[] psmLengthByte = new byte[2];
            inputStream.read(psmLengthByte);
            int hkLength = (psmLengthByte[0] & 0xFF) << 8 | (psmLengthByte[1] & 0xFF);
            inputStream.skip(hkLength);
        } else {
            inputStream.reset();
        }
    }

    /**
     * 读取psh头 psm头 psm标题 及数据
     *
     * @return
     */
    private void readPSHAndPSMAndPSMT() throws IOException, InterruptedException {
        //ps头起始标志
        byte[] psHeaderStartCode = new byte[4];
        inputStream.mark(psHeaderStartCode.length);
        inputStream.read(psHeaderStartCode);
        //判断是否是ps头
        if ((psHeaderStartCode[0] & 0xFF) == 0x00 && (psHeaderStartCode[1] & 0xFF) == 0x00 && (psHeaderStartCode[2] & 0xFF) == 0x01 && (psHeaderStartCode[3] & 0xFF) == 0xBA) {
            byte[] stuffingLengthByte = new byte[1];
            inputStream.skip(9);
            inputStream.read(stuffingLengthByte);
            int stuffingLength = stuffingLengthByte[0] & 0x07;
            inputStream.skip(stuffingLength);
            //ps头起始标志
            byte[] psSystemHeaderStartCode = new byte[4];
            inputStream.mark(psSystemHeaderStartCode.length);
            inputStream.read(psSystemHeaderStartCode);
            //PS system header 系统标题
            if ((psSystemHeaderStartCode[0] & 0xFF) == 0x00 && (psSystemHeaderStartCode[1] & 0xFF) == 0x00 && (psSystemHeaderStartCode[2] & 0xFF) == 0x01 && (psSystemHeaderStartCode[3] & 0xFF) == 0xBB) {
                byte[] psSystemLengthByte = new byte[1];
                //ps系统头长度
                inputStream.read(psSystemLengthByte);
                int psSystemLength = psSystemLengthByte[0] & 0xFF;
                //跳过ps系统头
                inputStream.skip(psSystemLength);
                inputStream.read(psSystemHeaderStartCode);
            }
            //判断是否是psm系统头 则为IDR帧
            if ((psSystemHeaderStartCode[0] & 0xFF) == 0x00 && (psSystemHeaderStartCode[1] & 0xFF) == 0x00 && (psSystemHeaderStartCode[2] & 0xFF) == 0x01 && (psSystemHeaderStartCode[3] & 0xFF) == 0xBC) {
                //psm头长度可以
                byte[] psmLengthByte = new byte[2];
                inputStream.read(psmLengthByte);
                int psmLength = (psmLengthByte[0] & 0xFF) << 8 | (psmLengthByte[1] & 0xFF);
                inputStream.mark(psmLength);
                //获取音视频类型
                if (videoType == 0 || audioType == 0) {
                    //自定义复合流描述
                    byte[] detailStreamLengthByte = new byte[2];
                    inputStream.skip(2);
                    inputStream.read(detailStreamLengthByte);
                    int detailStreamLength = (detailStreamLengthByte[0] & 0xFF) << 8 | (detailStreamLengthByte[1] & 0xFF);
                    inputStream.skip(detailStreamLength + 2);
                    byte[] videoStreamTypeByte = new byte[1];
                    inputStream.read(videoStreamTypeByte);
                    videoType = videoStreamTypeByte[0] & 0xFF;
                    inputStream.skip(1);
                    byte[] videoStreamDetailLengthByte = new byte[2];
                    inputStream.read(videoStreamDetailLengthByte);
                    int videoStreamDetailLength = (videoStreamDetailLengthByte[0] & 0xFF) << 8 | (videoStreamDetailLengthByte[1] & 0xFF);
                    inputStream.skip(videoStreamDetailLength);
                    byte[] audioStreamTypeByte = new byte[1];
                    inputStream.read(audioStreamTypeByte);
                    audioType = audioStreamTypeByte[0] & 0xFF;
                    inputStream.reset();
                }
                inputStream.skip(psmLength);
            } else {
                inputStream.reset();
            }
        } else {
            inputStream.reset();
        }
    }


    /**
     * 释放资源
     *
     * @return
     */
    public void release() {
        ZLM_API.mk_media_release(mkMedia);
        buffer.close();
    }
}